﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SmartRoute.Data;
using SmartRoute.Randomness;

namespace SmartRoute.Optimization
{
    [Serializable]
    public class GeneticAlgorithmGenome: ISolution<GeneticAlgorithmGenome>, ILocallySearchable<GeneticAlgorithmGenome>
    {
        protected GeneticAlgorithmGenome() {
        
        }

        #region Gene

        [Serializable]
        protected struct Gene
        {
            public int BusID;
            public Stop Stop;
            public bool isForBus(int totalBuses, int busIndex)
            {
                return BusID % totalBuses == busIndex;
            }
            public Gene(int busID, Stop stop)
            {
                BusID = busID;
                Stop = stop;
            }

            public Gene mutate(RandomSource randomSource)
            {
                return new Gene(
                    this.BusID + (randomSource.coinflip() ? 1 : -1),
                    this.Stop);
            }

            public static Gene Crossover(RandomSource randomSource, Gene gene1, Gene gene2)
            {
                return randomSource.coinflip() ? gene1 : gene2;
            }

            public IEnumerable<Gene> neighbors()
            {
                yield return new Gene(this.BusID - 1, this.Stop);
                yield return new Gene(this.BusID - 1, this.Stop);
                yield break;
            }
        }

        #endregion

        #region Chromosome

        [Serializable]
        protected struct Chromosome
        {
            public Location Destination;
            public Gene[] genes;

            public Chromosome(RandomSource randomSource, School toBuildChromosomeFor)
            {
                genes = (from stop in toBuildChromosomeFor.Stops
                         select new Gene(randomSource.Next(), stop)).ToArray();
                Destination = toBuildChromosomeFor.Address;
            }

            private Chromosome(Location destination, IEnumerable<Gene> copiedGenes)
            {
                Destination = destination;
                genes = copiedGenes.ToArray();
            }

            public Chromosome mutate(RandomSource randomSource)
            {
                var ix = randomSource.Next(0, this.genes.Length);
                var intermediateArray = this.genes.ToArray();
                intermediateArray[ix] = intermediateArray[ix].mutate(randomSource);
                return new Chromosome(this.Destination, intermediateArray);
            }

            public Bus solveForBus(int totalBuses, int index, Bus toSolveFor)
            {
                return Bus.ViaPreviousBus(toSolveFor,Destination,
                    (from gene in genes
                     where gene.isForBus(totalBuses,index)
                     select gene.Stop));
            }

            public static Chromosome Crossover(RandomSource rand, Chromosome chromosome1, Chromosome chromosome2)
            {
                if (chromosome1.genes.Length != chromosome2.genes.Length || chromosome1.Destination != chromosome2.Destination)
                    throw new ArgumentOutOfRangeException("Genome's chromosomes must be of the same length and have the same destination to cross over");
                var newChromosome = new Gene[chromosome1.genes.Length];
                for(int i = 0; i < chromosome1.genes.Length; i++) newChromosome[i] = Gene.Crossover(rand,chromosome1.genes[i],chromosome2.genes[i]);
                return new Chromosome(chromosome1.Destination, newChromosome);
            }

            public IEnumerable<Chromosome> neighbors()
            {
                for(int i = 0; i < genes.Length; i++)
                {
                    foreach (Gene mutant in genes[i].neighbors())
                    {
                        var tempArr = genes.ToArray();
                        tempArr[i] = mutant;
                        yield return new Chromosome(Destination, tempArr);
                    }
                }
            }
        }

        #endregion

        #region Private Members

        protected Chromosome[] geneticCode;

        #endregion

        #region Constructors

        public GeneticAlgorithmGenome(RandomSource randomSource, IEnumerable<School> toBuildChromosomesFor)
        {
            geneticCode = (from school in toBuildChromosomesFor
                           select new Chromosome(randomSource, school)).ToArray();
        }

        private GeneticAlgorithmGenome(IEnumerable<Chromosome> genes)
        {
            geneticCode = genes.ToArray();
        }

        #endregion

        #region ISolution<GeneticAlgorithmGenome> Members

        public GeneticAlgorithmGenome Build(RandomSource m, IEnumerable<School> school)
        {
            return new GeneticAlgorithmGenome(m, school);
        }

        public GeneticAlgorithmGenome Mutate(RandomSource m)
        {
            var intermediateArray = this.geneticCode.ToArray();
            var ix = m.Next(0, intermediateArray.Length);
            intermediateArray[ix] = intermediateArray[ix].mutate(m);
            return new GeneticAlgorithmGenome(intermediateArray);
        }

        //public IEnumerable<IEnumerable<Bus>> SolveFor(IEnumerable<Bus> buses)
        //{
        //    var totalBuses = buses.Count();
        //    return buses.Select((bus, i) => solveForSingleBus(totalBuses, bus, i));
        //}

        public IEnumerable<IEnumerable<Bus>> SolveFor(IEnumerable<Bus> buses)
        {
            var busArr = buses.ToArray();
            foreach (var chromosome in geneticCode)
            {
                yield return busArr.Select((x,i) => chromosome.solveForBus(busArr.Length, i, x));
            }
        }

        private IEnumerable<Bus> solveForSingleBus(int totalBuses, Bus bus, int busIndex)
        {
            var scanState = bus;
            foreach (var chromosome in geneticCode)
            {
                yield return scanState;
                scanState = chromosome.solveForBus(totalBuses, busIndex, scanState);
            }
        }

        #endregion

        protected Gene[] getAllGenes()
        {
            return this.geneticCode.SelectMany(x => x.genes).ToArray();
        }

        protected static Gene[,] asMatrix(IEnumerable<GeneticAlgorithmGenome> population)
        {
            var preMatrix = population.Select(x => x.getAllGenes()).ToArray();
            var lengthOfFirst = preMatrix.First().Length;
            if (preMatrix.Any(x => x.Length != lengthOfFirst))
                throw new ArgumentOutOfRangeException(
                    "Genes in population must have the same length as each other");
            var matrix = new Gene[preMatrix.Length, lengthOfFirst];
            for (int chromosome = 0; chromosome < preMatrix.Length; chromosome++)
                for (int gene = 0; gene < preMatrix.First().Length; gene++)
                    matrix[chromosome, gene] = preMatrix[chromosome][gene];
            return matrix;
        }

        public static GeneticAlgorithmGenome Crossover(RandomSource rand, GeneticAlgorithmGenome genome1, GeneticAlgorithmGenome genome2)
        {
            if (genome1.geneticCode.Length != genome2.geneticCode.Length) throw new ArgumentOutOfRangeException(
                 "Genomes must have the same number of chromosomes to cross over");
            var resultGenome = new Chromosome[genome1.geneticCode.Length];
            for (int i = 0; i < genome1.geneticCode.Length; i++) resultGenome[i] = Chromosome.Crossover(rand, genome1.geneticCode[i], genome2.geneticCode[i]);
            return new GeneticAlgorithmGenome(resultGenome);
        }


        #region ILocallySearchable<GeneticAlgorithmGenome> Members

        public IEnumerable<GeneticAlgorithmGenome> NeighborSearch()
        {
            for (int i = 0; i < geneticCode.Length; i++)
            {
                foreach (Chromosome mutant in geneticCode[i].neighbors())
                {
                    var tempArr = geneticCode.ToArray();
                    tempArr[i] = mutant;
                    yield return new GeneticAlgorithmGenome(tempArr);
                }
            }
        }

        public IEnumerable<GeneticAlgorithmGenome> DeepLocalSearch(uint depth)
        {
            return ILocallySearchableFunctions.DefaultDeepLocalSearch(this, depth);
        }

        #endregion
    }
}
